-- LocationM
-- Created by zouyb
-- 定位模块

module("LocationM", package.seeall);

-- 默认编码位数
-- （5位编码精度范围2400米，6位编码610米，7位编码76米，8位为19米，可根据精度需要选择编码位数）
local curPrecision = 6;

-- 当前geohash编码
local curGeoHashStr = "";

-- 获取周围玩家回调
local getNearbyFunc = nil;

-- 当前数据版本信息
local curVersion = "";

-- 初始化
function init()
    if isAndroidPlatform() then
        require "game/sdk/android/LocationAgent";
        LocationAgent.init();
    elseif isIosPlatform() then
        require "game/sdk/ios/LocationAgent";
        LocationAgent.init();
    end
end

-- 设置编码位数
function setPrecision(precision)
    curPrecision = precision;
end

-- 准备阶段，查看是否已经拥有定位权限
function prepare()
    require "game/base/encrypt/GeoHash";

    if isAndroidPlatform() then
        -- GPS服务是否开启
        if not LocationAgent.isServiceEnabled() then
            -- 通知玩家开启GPS定位方法
            EventMgr.fire(event.ANDROID_ENABLE_GPS_DESC);
            return false;
        end

        -- 判断是否拥有权限
        local status = LocationAgent.getPermissionStatus();
        if LocationAgent.STATUS_NOT_DETERMINED == status
            or LocationAgent.STATUS_DENIED == status then

            local function callback()
                -- 请求获取权限
                print("请求获取权限");
                LocationAgent.requestPermissions();
            end

            -- 向玩家说明获取权限用途
            EventMgr.fire(event.LOCATION_PERMISSION_DESC, callback);
            return false;
        elseif LocationAgent.STATUS_DISABLED == status then
            -- 通知玩家无法定位
            EventMgr.fire(event.ANDROID_LOCATION_PERMISSION_DISABLED);
        end

        return true;
    elseif isIosPlatform() then
        if not LocationAgent.isServiceEnabled() then
            EventMgr.fire(event.IOS_ENABLE_GPS_DESC);
            return false;
        end

        -- 获取当前定位权限
        local status = LocationAgent.getAuthorizationStatus();
        if LocationAgent.STATUS_NOT_DETERMINED == status then
            local function callback()
                -- 请求获取权限
                print("请求获取权限");
                LocationAgent.requestAuthorization();
            end

            -- 向玩家说明获取权限用途
            EventMgr.fire(event.LOCATION_PERMISSION_DESC, callback);
            return false;
        elseif LocationAgent.STATUS_RESTRICTED == status
            or LocationAgent.STATUS_DENIED == status then
            -- 通知玩家无法定位
            EventMgr.fire(event.IOS_NO_LOCATION_PERMISSION);
            return false;
        else
            return true;
        end
    elseif isWindowsPlatform() then
        -- windows作为测试用，直接返回true
        return true;
    else
        -- 其他平台暂不支持
        alert(getLocStr("disable_function"));
        return false;
    end
end

-- 发起定位
function start(callback)
    -- 调用手机上获取GPS功能模块
    if isAndroidPlatform() then
        LocationAgent.requestLocation();
    elseif isIosPlatform() then
        LocationAgent.requestLocation();
    end

    if isWindowsPlatform() then
        -- windows下模拟随机坐标，厦门市软件园二期周边
        local minLat = 24.4830622325;
        local maxLat = 24.4918751161;
        local minLon = 118.1751988234;
        local maxLon = 118.1876629859;

        local lat = minLat + math.random(0, 1)* (maxLat - minLat);
        local lon = minLon + math.random(0, 1)* (maxLon - minLon);

        onSuccess(lat, lon);
    end
end

-- 获取坐标回调
function onSuccess(lat, lon)
    -- 生成geohash串，上报到服务器
    local fullHashStr = GeoHash.encode(lat, lon);

    -- 截取精度位数
    curGeoHashStr = string.sub(fullHashStr, 1, curPrecision);

    -- 上报给服务器
    Operation.cmd_upload_location_data(curGeoHashStr, fullHashStr);
end

-- 获取失败
function onFail(reason)
    EventMgr.fire(event.GET_LOCATION_FAIL, reason);
end

-- 获取周围其他玩家信息
-- 该接口调用频率不要小于3秒
function getNearbyPlayers(callback, limitNum)
    -- 获取周围八个格子的geohash编码
    local hashStrList = getGeoHashStr();

    if #hashStrList == 0 then
        return;
    end

    -- 从服务器请求这9个格子内的玩家
    Operation.cmd_get_nearby_infos(hashStrList, curVersion, limitNum);

    -- 设置回调
    getNearbyFunc = callback;
end

-- 获取9格编码
function getGeoHashStr()
    if curGeoHashStr == "" then
        return {};
    end

    local hashStrList = GeoHash.neighbours(curGeoHashStr);
    hashStrList = table.values(hashStrList);
    table.insert(hashStrList, 1, curGeoHashStr);
    return hashStrList;
end

-- 获取到周围玩家信息
function onGetNearbySuccess(infoList, version)
    local me = infoList[1];
    me["distance"] = 0;
    me["offsetX"] = 0;
    me["offsetY"] = 0;

    local num = #infoList;

    local myHash = me["full_hash_str"];
    if not myHash then
        return;
    end

    local myLatLon = GeoHash.decode(myHash);
    for i = 2, num do
        -- 获取其他玩家的信息
        local hash = infoList[i]["full_hash_str"];
        local latLon = GeoHash.decode(hash);

        -- 计算和自己的距离
        local distance = calcDistance(myLatLon.lat, myLatLon.lon, latLon.lat, latLon.lon);
        infoList[i]["distance"] = distance;

        -- 计算假设自己是坐标原点时，其他玩家的坐标。默认比例 1像素=1米
        -- 拿到坐标后，如果发现屏幕显示不下，可以在自己的回调函数中按比例调整

        -- 方位角
        local d = gps2d(myLatLon.lat, myLatLon.lon, latLon.lat, latLon.lon);

        -- 计算相对于自己的X和Y偏移
        infoList[i]["offsetX"] = distance * math.cos(d);
        infoList[i]["offsetY"] = distance * math.sin(d);
    end

    curVersion = version;

    -- printTable(infoList, "返回周围玩家信息");

    if getNearbyFunc then
        getNearbyFunc(infoList);
    end
end

local function convertDegreesToRadians(degrees)
      return degrees * math.pi / 180;
end
local function convertRadiansToDegrees(radian)
      return radian * 180.0 / math.pi;
end

-- 计算两个经纬度坐标之间的距离
-- 返回单位：米
function calcDistance(lat1, lon1, lat2, lon2)
    local function haverSin(theta)
        local v = math.sin(theta / 2);
        return v * v;
    end

    local EARTH_RADIUS = 6371.0; -- km 地球半径 平均值，千米

    -- 用haversine公式计算球面两点间的距离。
    -- 经纬度转换成弧度
    lat1 = convertDegreesToRadians(lat1);
    lon1 = convertDegreesToRadians(lon1);
    lat2 = convertDegreesToRadians(lat2);
    lon2 = convertDegreesToRadians(lon2);

     -- 差值
    local vLon = math.abs(lon1 - lon2);
    local vLat = math.abs(lat1 - lat2);

    -- h is the great circle distance in radians, great circle就是一个球体上的切面，它的圆心即是球心的一个周长最大的圆。
    local h = haverSin(vLat) + math.cos(lat1) * math.cos(lat2) * haverSin(vLon);
    local distance = 2 * EARTH_RADIUS * math.asin(math.sqrt(h));

    -- 千米转成米
    return distance * 1000;
end

-- 计算两个经纬度坐标之间方位角
function gps2d(lat1, lon1, lat2, lon2)
    local d = 0;
    lat1 = convertDegreesToRadians(lat1);
    lon1 = convertDegreesToRadians(lon1);
    lat2 = convertDegreesToRadians(lat2);
    lon2 = convertDegreesToRadians(lon2);

    d = math.sin(lat1)*math.sin(lat2)+math.cos(lat1)*math.cos(lat2)*math.cos(lon2-lon1);
    d = math.sqrt(1-d*d);
    d = math.cos(lat2)*math.sin(lon2-lon1)/d;
    d = math.asin(d)*180/math.pi;
    return d;
end

